# 'Friendly Flow' is a Server Framework for Python 2.5 and up
# Copyright (C) Xaba Software http://www.xaba.nl/
#
# This file is part of 'Friendly Flow'
#
# 'Friendly Flow' is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 'Friendly Flow' is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with 'Friendly Flow'; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

from select import select as python_select
from time import time as python_time

class sleep(object):
    def __init__(self, doneAt):
        self.doneAt = doneAt

class read(object):
    def __init__(self, descriptor):
        """A file descriptor is either a socket or file object, or a small integer
gotten from a fileno() method call on one of those."""
        if hasattr(descriptor, "fileno"):
            self.descriptor = descriptor.fileno()
        else:
            self.descriptor = descriptor
        
    def __eq__(self, other):
        return type(other) == read and self.descriptor == other.descriptor
    
    def __repr__(self):
        return "blocking read(%s)" % self.descriptor

class write(object):
    def __init__(self, descriptor):
        """A file descriptor is either a socket or file object, or a small integer
gotten from a fileno() method call on one of those."""
        if hasattr(descriptor, "fileno"):
            self.descriptor = descriptor.fileno()
        else:
            self.descriptor = descriptor
    
    def __eq__(self, other):
        return type(other) == write and self.descriptor == other.descriptor

    def __repr__(self):
        return "blocking write(%s)" % self.descriptor

class fork(object):
    
    def __init__(self, thread):
        self.thread = thread

class Scheduler(object):
    
    def __init__(self, threads, select=python_select, time=python_time):
        self._sleeping = [(0, thread) for thread in threads]
        self._readers = []
        self._writers = []
        self._select = select
        self._time = time #todo: check precision of the clock for time(); what about moving clocks?
    
    def run(self):
        while self._sleeping or self._readers or self._writers: #not tested...
            self.step()

    def step(self):
        self._newSleeping = []
        self._newReaders = []
        self._newWriters = []
        
        if self._sleeping:
            sleepTime = max(0, self._sleeping[0][0] - self._time())
        else:
            sleepTime = None
        
        if self._readers or self._writers:
            rReady, wReady, ignored = self._select(
                dict(self._readers).keys(),
                dict(self._writers).keys(),
                [], sleepTime)
            
            for descriptor, thread in self._readers:
                if descriptor in rReady:
                    self._next(thread)
                else:
                    self._newReaders.append((descriptor, thread))
        
            for descriptor, thread in self._writers:
                if descriptor in wReady:
                    self._next(thread)
                else:
                    self._newWriters.append((descriptor, thread))
        
        else:
            if sleepTime != 0:
                self._select([], [], [], sleepTime)
        
        now = self._time()
        for doneAt, thread in self._sleeping:
            if doneAt <= now:
                self._next(thread)
            else:
                self._newSleepingAppend(doneAt, thread)
        
        self._sleeping = self._newSleeping
        self._readers = self._newReaders
        self._writers = self._newWriters

    def _next(self, thread):
        try:
            next = thread.next()
        except StopIteration:
            return
        
        if next == None:
            self._newSleepingAppend(self._time(), thread)
        elif isinstance(next, sleep):
            self._newSleepingAppend(next.doneAt, thread)
        elif isinstance(next, read):
            self._newReaders.append((next.descriptor, thread))
        elif isinstance(next, write):
            self._newWriters.append((next.descriptor, thread))
        elif isinstance(next, fork):
            self._newSleepingAppend(self._time(), next.thread)
            self._newSleepingAppend(self._time(), thread)
        else:
            raise Exception("unexpected token: %s" % next)
    
    def _newSleepingAppend(self, doneAt, thread):
        i = 0
        while i < len(self._newSleeping):
            if self._newSleeping[i][0] > doneAt:
                break
            i += 1
        self._newSleeping.insert(i, (doneAt, thread))
